package com.abel.bigwater.dataserver.model

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.hgd.AnalogChannel
import com.abel.bigwater.dataserver.model.hgd.HgdChannelType
import com.abel.bigwater.dataserver.model.hgd.HgdUdpData
import com.abel.bigwater.dataserver.model.hgd.PulseChannel
import com.abel.bigwater.dataserver.model.kming.*
import com.abel.bigwater.dataserver.model.qdies.*
import org.slf4j.Logger
import java.util.*
import kotlin.collections.ArrayList

object PojoHelper {
    /**
     * convert iwg-packet to pojo and store to db. They will be fetched and sent out by kming-virtual-devices.
     */
    fun toPojo(dt: HgdUdpData, rtuMeter: FlowRtuMeter?): FlowDataPojo {
        val p1 = FlowDataPojo().apply {
            recvTime = Date()
            station = dt.station
            startTime = dt.startTime
            period = dt.period
            count = dt.count

            fmt = dt.fmt
            voltage = dt.voltage
            signal = dt.signal
            uploadTimes = dt.uploadTimes
            devInfoSoft = dt.devInfoSoft
        }

        for (ch in dt.channelList) {
            when {
                ch is PulseChannel -> {
                    val hc = FlowChannelPojo<Number>().apply {
                        channelType = HgdChannelType.PULSE_TYPE
                        channel = ch.channel?.toInt()
                        yest = ch.yest
                        values = ArrayList()
                        values!!.addAll(ch.values)
                    }

                    p1.pulseList.add(hc)

                    // check other channels.
                    when {
                        ch.type == rtuMeter?.forwardChannel?.channelType && ch.channel == rtuMeter.forwardChannel?.channel -> {
                            p1.forwardData = hc
                        }
                        ch.type == rtuMeter?.revertChannel?.channelType && ch.channel == rtuMeter.revertChannel?.channel -> {
                            p1.revertData = hc
                        }
                        ch.type == rtuMeter?.netChannel?.channelType && ch.channel == rtuMeter.netChannel?.channel -> {
                            p1.netData = hc
                        }
                    }
                }

                ch is AnalogChannel -> {
                    val hc = FlowChannelPojo<Double>().apply {
                        channelType = HgdChannelType.ANALOG_TYPE
                        channel = ch.channel?.toInt()
                        values = ArrayList<Double>().also {
                            it.addAll(ch.values.map { a -> a.toDouble() })
                        }
                    }

                    p1.analogList.add(hc)

                    // check other channels
                    when {
                        ch.type == rtuMeter?.flowChannel?.channelType && ch.channel == rtuMeter.flowChannel?.channel -> {
                            p1.flowData = hc
                        }
                        ch.type == rtuMeter?.pressureChannel?.channelType && ch.channel == rtuMeter.pressureChannel?.channel -> {
                            p1.pressureData = hc
                        }
                    }
                }
            }
        }

        return p1
    }

    /**
     * convert from kming-packet to pojo and save to db. It may be fetched by other APPs.
     */
    fun toPojo(dt: KmingPacket): FlowDataPojo? {
        val p1 = FlowDataPojo().apply {
            recvTime = Date()
            station = dt.termCode.fmt
        }

        when (dt.data) {
            is TermSendData -> {
                val data = dt.data as TermSendData
                p1.startTime = data.startTime
                p1.period = data.sampleSeconds!!.toShort().div(60).toShort()
                p1.count = data.dataCount ?: 0

                // forward channel
                val fwdCh = data.channelDataList.find {
                    it.channel.code in arrayOf(
                            KmingChannelType.MeterForwardInt.code,
                            KmingChannelType.MeterForwardFloat,
                            KmingChannelType.MeterForwardDouble,
                            KmingChannelType.MeterForwardIntShort)
                }
                if (fwdCh != null) {
                    p1.forwardData = FlowChannelPojo<Number>().also {
                        it.channelType = HgdChannelType.PULSE_TYPE
                        it.channel = fwdCh.channel.code
                        it.values = ArrayList()
                        it.values!!.addAll(fwdCh.dataList)

                        p1.pulseList.add(it)
                    }
                }

                // revert channel
                val rvtCh = data.channelDataList.find {
                    it.channel.code in arrayOf(
                            KmingChannelType.MeterRevertInt.code,
                            KmingChannelType.MeterRevertFloat.code,
                            KmingChannelType.MeterRevertDouble.code,
                            KmingChannelType.MeterRevertIntShort.code)
                }
                if (rvtCh != null) {
                    p1.revertData = FlowChannelPojo<Number>().also {
                        it.channelType = HgdChannelType.PULSE_TYPE
                        it.channel = rvtCh.channel.code
                        it.values = ArrayList()
                        it.values!!.addAll(rvtCh.dataList)

                        p1.pulseList.add(it)
                    }
                }

                // net channel
                val netCh = data.channelDataList.find {
                    it.channel.code in arrayOf(
                            KmingChannelType.MeterNetInt.code,
                            KmingChannelType.MeterNetDouble.code,
                            KmingChannelType.MeterNetDouble.code,
                            KmingChannelType.MeterNetIntShort.code
                    )
                }
                if (netCh != null) {
                    p1.netData = FlowChannelPojo<Number>().also {
                        it.channelType = HgdChannelType.PULSE_TYPE
                        it.channel = netCh.channel.code
                        it.values = ArrayList()
                        it.values!!.addAll(netCh.dataList)

                        p1.pulseList.add(it)
                    }
                }

                // pressure
                val preCh = data.channelDataList.find { it.channel.code == KmingChannelType.MeterPressureFloat.code }
                if (preCh != null) {
                    p1.pressureData = FlowChannelPojo<Double>().also {
                        it.channelType = HgdChannelType.ANALOG_TYPE
                        it.channel = preCh.channel.code
                        it.values = ArrayList()
                        it.values!!.addAll(preCh.dataList.map { p -> p.toDouble() })

                        p1.analogList.add(it)
                    }
                }

                // flow
                val flowCh = data.channelDataList.find { it.channel.code == KmingChannelType.MeterFlow.code }
                if (flowCh != null) {
                    p1.flowData = FlowChannelPojo<Double>().also {
                        it.channelType = HgdChannelType.ANALOG_TYPE
                        it.channel = flowCh.channel.code
                        it.values = ArrayList()
                        it.values!!.addAll(flowCh.dataList.map { f -> f.toDouble() })

                        p1.analogList.add(it)
                    }
                }
            }

            is TermUploadReqData -> {
                val data = dt.data as TermUploadReqData
                p1.station = dt.termCode.fmt
                p1.startTime = Date()
                p1.voltage = data.rtuVolt!!
                p1.signal = data.rssi!!
            }
        }

        return p1
    }

    /**
     * convert from kming-packet to pojo and save to db. It may be fetched by other APPs.
     */
    fun toPojo(dt: D10Packet, lgr: Logger? = null): FlowDataPojo? {
        val p1 = FlowDataPojo().apply {
            recvTime = Date()
            station = dt.address?.bcdString
        }

        when (dt.data) {
            is DtenAskNowData -> {
                val data = dt.data as DtenAskPulseNowData
                p1.station = dt.address?.bcdString
                p1.fmt = dt.text
                p1.startTime = data.sampleTime!!.toDate()
                p1.count = 1

                // forward channel
                p1.forwardData = FlowChannelPojo<Number>().apply {
                    channelType = HgdChannelType.PULSE_TYPE
                    channel = data.internalAddr!!.toInt()
                    values = arrayListOf(data.reading!!)
                }
            }

            is DtenAskPulseHistData -> {
                val data = dt.data as DtenAskPulseHistData
                p1.station = dt.address?.bcdString
                p1.fmt = dt.text
                p1.startTime = data.lastReadTime
                p1.count = data.readList?.size?.toShort() ?: 0

                p1.forwardData = FlowChannelPojo<Number>().apply {
                    channelType = HgdChannelType.PULSE_TYPE
                    channel = data.internalAddr!!.toInt()
                    values = ArrayList(data.readList!!.map { it as Number })
                }
            }

            is DtenAsk485HistData -> {

            }

            is DtenAskAnalogHistData -> {

            }

            else -> {
                lgr?.warn("not handled data: ${dt.data}")
            }
        }

        return p1
    }

    /**
     * convert flow data to list of kming-packets.
     */
    fun toKmingPacket(pojo: FlowDataPojo, rtuMeter: FlowRtuMeter?): ArrayList<KmingPacket> {
        val plst = ArrayList<KmingPacket>()
        var ccnt: Short = 0
        var dcnt: Short = 0

        // send-req
        val p1 = KmingPacket().apply {
            termCode.remote = RemoteType.PulseRemote
            termCode.size = SizeType.Horizon80
            termCode.codeBcd = BcdString.fromBcdString("00000000${pojo.station}".takeLast(8))

            sessionStatus.serialNo = 1

            data = TermUploadReqData().also {
                it.rtuVolt = pojo.voltage
                it.rssi = pojo.signal
                it.latitude = rtuMeter?.latitude
                it.longitude = rtuMeter?.longitude

                it.channelCount = 0
                if (pojo.forwardData != null) {
                    it.channelCount += 1
                    it.channelList.add(KmingChannelType.MeterForwardInt)

                    dcnt = pojo.forwardData!!.values?.size?.toShort() ?: 0
                }

                if (pojo.revertData != null) {
                    it.channelCount += 1
                    it.channelList.add(KmingChannelType.MeterRevertInt)

                    dcnt = pojo.revertData!!.values?.size?.toShort() ?: 0
                }

                if (pojo.netData != null) {
                    it.channelCount += 1
                    it.channelList.add(KmingChannelType.MeterNetInt)

                    dcnt = pojo.netData!!.values?.size?.toShort() ?: 0
                }

                if (pojo.flowData != null) {
                    it.channelCount += 1
                    it.channelList.add(KmingChannelType.MeterFlow)

                    dcnt = pojo.flowData!!.values?.size?.toShort() ?: 0
                }

                if (pojo.pressureData != null) {
                    it.channelCount += 1
                    it.channelList.add(KmingChannelType.MeterPressureFloat)

                    dcnt = pojo.pressureData!!.values?.size?.toShort() ?: 0
                }

                // pulse-list to kming
                if (pojo.pulseList.isNotEmpty()) {
                    it.channelCount += 1
                    dcnt = pojo.pulseList[0].values?.size?.toShort() ?: 0

                    it.channelList.add(KmingChannelType.MeterForwardInt)

                    if (pojo.pulseList.size > 1) {
                        it.channelCount += 1
                        it.channelList.add(KmingChannelType.MeterRevertInt)
                    }

                    if (pojo.pulseList.size > 2) {
                        it.channelCount += 1
                        it.channelList.add(KmingChannelType.MeterNetInt)
                    }
                }

                if (pojo.analogList.isNotEmpty()) {
                    it.channelCount += 1

                    it.channelList.add(KmingChannelType.MeterPressureFloat)

                    if (pojo.analogList.size > 1) {
                        it.channelList.add(KmingChannelType.MeterFlow)
                    }
                }

                ccnt = it.channelCount.toShort()
            }

            plst.add(this)
        }

        // send-data.
        KmingPacket().apply {
            termCode = p1.termCode
            sessionStatus.serialNo = 1 + (p1.sessionStatus.serialNo ?: 0)

            data = TermSendData().also {
                it.startTime = pojo.startTime
                it.channelCount = ccnt
                it.sampleSeconds = pojo.period.toLong().times(60)
                it.dataCount = dcnt
                it.channelDataList

                if (pojo.forwardData != null) {
                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterForwardInt, pojo.forwardData!!.values!!))
                }

                if (pojo.revertData != null) {
                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterRevertInt, pojo.revertData!!.values!!))
                }

                if (pojo.netData != null) {
                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterNetInt, pojo.netData!!.values!!))
                }

                if (pojo.flowData != null) {
                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterFlow, ArrayList(pojo.flowData!!.values!!)))
                }

                if (pojo.pressureData != null) {
                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterPressureFloat, ArrayList(pojo.pressureData!!.values!!))
                    )
                }

                // pulse.
                if (pojo.pulseList.isNotEmpty()) {
                    // find forward
                    val fwd = pojo.pulseList.find {
                        it.channel == rtuMeter?.forwardChannel?.channel?.toInt()
                    } ?: pojo.pulseList[0]

                    it.channelDataList.add(
                            KmingChannelData(KmingChannelType.MeterForwardInt, fwd.values ?: ArrayList())
                    )

                    // find revert
                    val rvt = pojo.pulseList.find {
                        it.channel == rtuMeter?.revertChannel?.channel?.toInt()
                    } ?: pojo.pulseList.drop(1).firstOrNull()
                    if (rvt != null) {
                        it.channelDataList.add(KmingChannelData(KmingChannelType.MeterRevertInt,
                                rvt.values ?: ArrayList()))
                    }

                    // net
                    val net = pojo.pulseList.find {
                        it.channel == rtuMeter?.netChannel?.channel?.toInt()
                    } ?: pojo.pulseList.drop(2).firstOrNull()
                    if (net != null) {
                        it.channelDataList.add(KmingChannelData(KmingChannelType.MeterNetInt,
                                net.values ?: ArrayList()))
                    }
                }

                // pressure.
                if (pojo.analogList.isNotEmpty()) {
                    val pre = pojo.analogList.find {
                        it.channel == rtuMeter?.pressureChannel?.channel?.toInt()
                    } ?: pojo.analogList.first()

                    it.channelDataList.add(KmingChannelData(KmingChannelType.MeterPressureFloat,
                            ArrayList(pre.values)))
                }
            }

            plst.add(this)
        }

        return plst
    }

    const val COL_FLOW_DATA = "hgdFlowData"

    const val KEY_STATION = "station"
    const val KEY_START_TIME = "startTime"
    const val KEY_SEND_COUNT = "sendCount"
    const val KEY_SEND_TIME = "sendTime"
    const val KEY_RECV_TIME = "recvTime"
}